﻿#include "Snake.h"

// 定位光标
void SetPos(int x, int y)
{
	// 获取标准输出设备的句柄
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);

	// 定位光标
	COORD pos = { x,y };
	SetConsoleCursorPosition(houtput, pos);
}

void WelcomeToGame()
{
	// 创建第一个画面
	SetPos(38, 12);
	wprintf(L"欢迎来到贪吃蛇小游戏");
	SetPos(40, 20);
	system("pause");
	system("cls");
	// 创建第二个画面
	SetPos(20, 12);
	wprintf(L"用 ↑ . ↓ . ← . → 分别控制蛇的移动， F3为加速，F4为减速");
	SetPos(40, 20);
	system("pause");
	system("cls");
}

void CreateMap()
{
	// 地图是在控制台创建的 X轴有100个字节，Y轴有31个  我们创建的地图大小是27行 58列
	// 墙是宽字符 ， 一个墙两个字节 。因此在X轴上打印29次即可
	
	// 打印上墙
	for (int i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}
	// 打印下墙
	SetPos(0, 26);
	for (int i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}
	// 打印左墙
	for (int i = 1; i <= 25; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}
	// 打印右墙
	for (int i = 1; i <= 25; i++)
	{
		SetPos(56, i);
		wprintf(L"%lc", WALL);
	}
}

void InitSnake(pSnake ps)
{
	// 我们初始化一条长度为5的蛇 需要5个节点
	pSnakeNode cur = NULL;
	for (int i = 0; i < 5; i++)
	{
		// 给蛇的节点申请空间
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL)
		{
			perror("InitSnake():malloc()");
			return;
		}
		cur->next = NULL;
		// 给蛇的节点初始化坐标
		cur->x = POS_X + 2 * i; // 必须是2的倍数
		cur->y = POS_Y;
		// 把蛇通过 头插法 链接起来
		if (ps->_pSanke == NULL) // 没有蛇头
		{
			ps->_pSanke = cur;
		}
		else
		{
			cur->next = ps->_pSanke; 
			ps->_pSanke = cur; // 让cur成为新的蛇头
		}
	}
	// 走到这里 蛇已经链接好了 我们将它打印出来
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	
	// 给蛇的属性初始化
	ps->_dir = RIGHT; //初始方向是右 因为蛇头在右边
	ps->_sroce = 0;
	ps->_status = OK;
	ps->_Food_Weight = 10;
	ps->_sleep_time = 200;
}

// 创建食物 
void CreateFood(pSnake ps)
{
	// 在墙内 并且 不与蛇身的坐标重叠 的情况下 随机生成食物
	pSnakeNode food = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (food == NULL)
	{
		perror("CreateFood():malloc()");
		return;
	}
	// 随机生产的坐标 在X轴上的坐标必须是2的倍数 x 的范围是 2~54  y的范围是 1 ~ 25
	int x = 0;
	int y = 0;
again:
	do
	{
		x = rand() % 53 + 2;
		y = rand() % 25 + 1;

	} while (x % 2 == 1); // 保证x是2的倍数

	// 还要确保x y 没有和蛇的坐标冲突
	pSnakeNode cur = ps->_pSanke;
	while (cur)
	{
		if (x == cur->x && y == cur->y)
		{
			goto again;
		}
		cur = cur->next;
	}
	// 这个时候x 和 y就是我们想要的
	food->x = x;
	food->y = y;
	food->next = NULL;

	// 定位食物坐标，并打印出来
	SetPos(food->x, food->y);
	wprintf(L"%lc", FOOD);

	ps->_Food = food;
}

// 初始化游戏
void GameStart(pSnake ps)
{
	// 1. 创建窗口大小，并隐藏光标
	// 创建窗口大小 并命名标题
	system("mode con cols=100 lines=31");
	system("title 贪吃蛇");

	// 隐藏光标
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO cursor_info = { 0 };
	GetConsoleCursorInfo(houtput, &cursor_info);
	cursor_info.bVisible = false;
	SetConsoleCursorInfo(houtput, &cursor_info);

	//2. 创建界面
	WelcomeToGame();

	//3. 绘制地图
	CreateMap();

	//4.初始化贪吃蛇
	InitSnake(ps);

	//5. 创建食物
	CreateFood(ps);
}

void PrintInfo()
{
	SetPos(62, 15);
	wprintf(L"%ls", L"不能撞到墙壁，也不能咬到自己");
	SetPos(62, 16);
	wprintf(L"%ls", L"用 ↑ . ↓ . ← . → 分别控制蛇的移动。");
	SetPos(62, 17);
	wprintf(L"%ls", L" F3为加速，F4为减速");
	SetPos(62, 18);
	wprintf(L"%ls", L"吃掉食物可以得分");
	SetPos(62, 19);
	wprintf(L"%ls", L"速度越快食物分数越高");
	SetPos(62, 20);
	wprintf(L"%ls", L"速度越慢食物分数越低");
	SetPos(62, 22);
	wprintf(L"%ls", L"ESC：退出游戏，space：暂停游戏");
	SetPos(62, 23);
	wprintf(L"%ls", L"再按一次space即可解除暂停");

}

void Pause()
{
	SetPos(62, 5);
	printf("再按一次空格即可解除暂停");
	while (1)
	{
		Sleep(200);
		if (KEY_PRESS(VK_SPACE))
		{
			SetPos(62, 5);
			printf("                          "); // 覆盖
			break;
		}
	}
}

int IsNextFood(pSnakeNode pnext, pSnake ps)
{
	return (pnext->x == ps->_Food->x && pnext->y == ps->_Food->y);
}

void EatFood(pSnakeNode pnext, pSnake ps)
{
	// 有食物的情况就让 食物直接头插到蛇上
	
	// 直接将食物头插到蛇身上【食物的本质也是蛇节点,只是打印的图案不一样而已】
	ps->_Food->next = ps->_pSanke;
	ps->_pSanke = ps->_Food;

	// 释放掉之前申请的新节点
	free(pnext);
	pnext = NULL;

	// 打印走完这一步的蛇
	pSnakeNode cur = ps->_pSanke;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	// 吃完食物要加分
	ps->_sroce += ps->_Food_Weight;

	// 食物吃了 要生产新的食物
	CreateFood(ps);
}

void NoFood(pSnakeNode pnext, pSnake ps)
{
	// 没有食物的情况下就要让之前申请的下一步的蛇节点头插到蛇身上 并且删除蛇的尾节点,覆盖掉尾节点的图案
	// 头插
	pnext->next = ps->_pSanke;
	ps->_pSanke = pnext;

	pSnakeNode cur = ps->_pSanke;
	pSnakeNode prev = ps->_pSanke; // 指向cur的前一个节点
	// 遍历链表 找到尾节点
	while (cur->next != NULL)
	{
		prev = cur;
		SetPos(prev->x, prev->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	// 走到这里prev指向的是倒数第二个节点 cur指向尾节点
	// 先覆盖蛇尾节点的图案
	SetPos(cur->x, cur->y);
	printf("  ");

	free(cur);
	cur = NULL;

	prev->next = NULL;
}

void Kill_By_Wall(pSnake ps)
{
	if (ps->_pSanke->x == 0 || ps->_pSanke->x == 56
		|| ps->_pSanke->y == 0 || ps->_pSanke->y == 26)
	{
		ps->_status = KILL_BY_WALL;
	}
}

void Kill_By_Self(pSnake ps)
{
	// 遍历链表 要保证不与任何一个蛇身相撞
	pSnakeNode cur = ps->_pSanke->next; // cur要从蛇的第二个节点开始
	while (cur)
	{
		if (cur->x == ps->_pSanke->x && cur->y == ps->_pSanke->y)
		{
			ps->_status = KILL_BY_SELF;
			break;
		}
		cur = cur->next;
	}
}

void SnakeMove(pSnake ps)
{
	// 首先创建一个新的蛇节点 作为下一步要走的地方
	pSnakeNode pnextnode = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pnextnode == NULL)
	{
		perror("SnakeMove():malloc()");
		return;
	}

	// 蛇想要走一步 首先要判断方向
	switch (ps->_dir)
	{
	case UP: // 上
		pnextnode->x = ps->_pSanke->x;
		pnextnode->y = ps->_pSanke->y - 1;
		break;
	case DOWN: // 下
		pnextnode->x = ps->_pSanke->x;
		pnextnode->y = ps->_pSanke->y + 1;
		break;
	case LEFT: // 左
		pnextnode->x = ps->_pSanke->x - 2;
		pnextnode->y = ps->_pSanke->y;
		break;
	case RIGHT: // 右
		pnextnode->x = ps->_pSanke->x + 2;
		pnextnode->y = ps->_pSanke->y;
		break;
	}

	// 判断完方向 要判断下一步是否有食物
	if (IsNextFood(pnextnode, ps))
	{
		// 有食物
		EatFood(pnextnode, ps);
		pnextnode = NULL;
	}
	else
	{
		// 没有食物
		NoFood(pnextnode, ps);
	}

	// 这个时候走了一步，要判断是否撞墙或者撞到自己
	Kill_By_Wall(ps);

	Kill_By_Self(ps);

}

// 运行游戏
void GameRun(pSnake ps)
{
	// 将游戏时会展示的信息 先展示无需变化的信息
	PrintInfo();

	int countspeed = 5; //初始速度为5档
	do 
	{
		// 展示会随着游戏变化而变化的信息
		SetPos(62, 11);
		printf("总分数：%d", ps->_sroce);
		SetPos(62, 12);
		printf("当前速度下食物的分数：%2d", ps->_Food_Weight);
		SetPos(62, 13);
		printf("当前速度档位:%2d", countspeed);

		// 检测按键
		if (KEY_PRESS(VK_UP) && ps->_dir 
			!= DOWN)
		{
			ps->_dir = UP;
		}
		else if (KEY_PRESS(VK_DOWN) && ps->_dir != UP)
		{
			ps->_dir = DOWN;
		}
		else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT)
		{
			ps->_dir = LEFT;
		}
		else if (KEY_PRESS(VK_RIGHT) && ps->_dir != LEFT)
		{
			ps->_dir = RIGHT;
		}
		else if (KEY_PRESS(VK_F3))
		{
			// 加速
			if (ps->_sleep_time > 50) // 限制只能加五档
			{
				ps->_sleep_time -= 30;
				ps->_Food_Weight += 2;
				countspeed++;
			}
		}
		else if (KEY_PRESS(VK_F4))
		{
			// 减速
			if (ps->_Food_Weight > 2) // 限制只能减五档
			{
				ps->_sleep_time += 30;
				ps->_Food_Weight -= 2;
				countspeed--;
			}
		}
		else if (KEY_PRESS(VK_SPACE))
		{
			// 暂停
			Pause();
		}
		else if (KEY_PRESS(VK_ESCAPE))
		{
			// 正常退出
			ps->_status = NORMAL_END;
		}

		// 检测完按键之后 就要走一步
		SnakeMove(ps);

		Sleep(ps->_sleep_time);
	} while (ps->_status == OK);
}


void GameEnd(pSnake ps)
{
	// 对不同的结束情况做出应对
	switch (ps->_status)
	{
	case NORMAL_END:
		SetPos(20, 20);
		printf("你选择了退出游戏");
		break;
	case KILL_BY_SELF:
		SetPos(20, 20);
		printf("你撞到了自己");
		break;
	case KILL_BY_WALL:
		SetPos(20, 20);
		printf("你撞到了墙");
		break;
	}

	// 释放资源
	pSnakeNode cur = ps->_pSanke;
	while (cur)
	{
		pSnakeNode next = cur->next;
		free(cur);
		cur = next;
	}
}